package algebra.visitors;

import java.util.LinkedList;
import java.util.List;

import option.KBVocabulary;

import algebra.op.ConstantOP;
import algebra.op.JoinOP;
import algebra.op.PrimaryOP;
import algebra.op.ProjectionOP;
import algebra.op.AlgebraicOP;
import algebra.op.ScanOP;
import algebra.op.SelectionOP;
//import algebra.query.OrderingDescription;
import algebra.query.indexing.DescriptionIndex;
import algebra.query.od.OrderDep;
import algebra.visitors.cursors.CACursor;

import reasoner.KBReason;
//import utils.Util;
import utils.exception.CoreReasoningException;
import utils.exception.OrderingDescriptionException;

import kb.DescriptionLogicsKB;
import entities.Individual;
import entities.assertion.ConceptAssertion;
import entities.assertion.StarConceptAssertion;
import entities.concept.AndConcept;
import entities.concept.Concept;

/**
 * The evaluation of a query returns a set of Concept Assertions, i.e., in the
 * form of a:C.
 * 
 * @author j55wu
 * 
 */
public class AlgebraicOPEvaluator {
	// private DescriptionLogicsKB kb;

	public AlgebraicOPEvaluator() {
	}

	/**
	 * A query is special iff the evaluation of the query returns {*:C}.
	 * 
	 * @param q
	 * @return
	 */
	public static boolean isSpecialAssertion(List<ConceptAssertion> res) {
		if (res.size() != 1) {
			return false;
		}
		return isSpecialAssertion(res.get(0));
	}

	public static boolean isSpecialAssertion(ConceptAssertion res) {
		return res instanceof StarConceptAssertion
				|| res.getIndividual().getName()
						.equals(KBVocabulary.SPECIAL_IND_SYMB);
	}

	// the input must be a planned query, i.e., informative query is invisible
	// in evaluation.
	public List<ConceptAssertion> visit(AlgebraicOP plannedQ)
			throws CoreReasoningException {
		// procedures for evaluation
		if (plannedQ instanceof PrimaryOP) {
			return EvaluatePQuery((PrimaryOP) plannedQ);
		} else if (plannedQ instanceof ConstantOP) {
			return EvaluateConcpetQuery((ConstantOP) plannedQ);
		} else if (plannedQ instanceof ScanOP) {
			return EvaluateScanQuery((ScanOP) plannedQ);
		} else if (plannedQ instanceof JoinOP) {
			return EvaluateIntersectionQuery((JoinOP) plannedQ);
		} else if (plannedQ instanceof ProjectionOP) {
			return EvaluateProjectionQuery((ProjectionOP) plannedQ);
		} else if (plannedQ instanceof SelectionOP) {
			return EvaluateSelectionQuery((SelectionOP) plannedQ);
		}
		{
			System.err.println("This type of queries cannot be evaluated: "
					+ plannedQ.getClass());
			return null;
		}

	}

	private List<ConceptAssertion> EvaluatePQuery(PrimaryOP q) {
		List<ConceptAssertion> res = new LinkedList<ConceptAssertion>();
		DescriptionLogicsKB kb = q.getPlanEvalKB();
		if (kb == null) {
			System.out.println("No KB found for this query to evaluate.");
			return res;
		}
		for (Individual ind : kb.getAbox().getABoxIndividuals()) {
			res.add(new ConceptAssertion(ind, KBVocabulary.CONCEPT_TOP));
		}
		return res;
	}

	public ConceptAssertion findIndividualInQueryResults(Individual ind,
			List<ConceptAssertion> cache) {
		for (ConceptAssertion ca : cache) {
			if (ca.getIndividual().equals(ind)) {
				return ca;
			}
		}
		return null;
	}

	private List<ConceptAssertion> EvaluateConcpetQuery(ConstantOP cq) {
		List<ConceptAssertion> res = new LinkedList<ConceptAssertion>();
		Concept c = cq.getQueryCocnept();
		res.add(new StarConceptAssertion(c));
		return res;
	}

	// the returned results NEVER have star assertions.
	private List<ConceptAssertion> EvaluateScanQuery(ScanOP sq)
			throws CoreReasoningException {
		List<ConceptAssertion> res = new LinkedList<ConceptAssertion>();
		List<ConceptAssertion> qs = visit(sq.getSubquery());
		if (isSpecialAssertion(qs)) {
			// star semantics
			Concept D = ((StarConceptAssertion) qs.get(0)).getConcept();
			List<ConceptAssertion> assertions = sq.getSecondaryIndex()
					.getDescriptionIndex().getAllConceptAssertions();
			for (ConceptAssertion ac : assertions) {
				ConceptAssertion ad = new ConceptAssertion(ac.getIndividual(),
						D);
				KBReason myreasoner = new KBReason(false);
				// note that Scan(PI^K) does not use K at all
				if (myreasoner.logicConsequence(ac, ad,
						DescriptionLogicsKB.newNullKB())) {
					res.add(ac);
				}
			}
		} else {
			// standard semantics
			// DescriptionIndex idx =
			// sq.getSecondaryIndex().getDescriptionIndex();
			// for(ConceptAssertion q:qs){
			// // res.addAll(idx.search(q, null));
			// res.addAll(CACursor.getResultSet(sq));
			// }
			res.addAll(CACursor.getResultSet(sq));
		}
		return res;
	}

	// see semantics: a: (C1 and C2), nested loop join
	private List<ConceptAssertion> EvaluateIntersectionQuery(JoinOP iq)
			throws CoreReasoningException {
		List<ConceptAssertion> res = new LinkedList<ConceptAssertion>();
		List<ConceptAssertion> q1 = visit(iq.getFirstSubQuery());
		List<ConceptAssertion> q2 = visit(iq.getSecondSubQuery());
		boolean bq1 = isSpecialAssertion(q1);
		boolean bq2 = isSpecialAssertion(q2);
		if (bq1 && bq2) {
			// both are star assertions
			Concept d1 = q1.get(0).getConcept();
			Concept d2 = q2.get(0).getConcept();
			res.add(new StarConceptAssertion(new AndConcept(d1, d2)));
		} else if (bq1 && !bq2) {
			Concept con = q1.get(0).getConcept();
			for (ConceptAssertion ca : q2) {
				res.add(new ConceptAssertion(ca.getIndividual(),
						new AndConcept(ca.getConcept(), con)));
			}
		} else if (!bq1 && bq2) {
			Concept con = q2.get(0).getConcept();
			for (ConceptAssertion ca : q1) {
				res.add(new ConceptAssertion(ca.getIndividual(),
						new AndConcept(ca.getConcept(), con)));
			}
		} else {
			// normal concept assertions in both sub-queries
			res.addAll(this.getNormalJoinResults(q1, q2,
					JoinAlgorithm.NESTED_LOOP));
		}
		return res;
	}

	private enum JoinAlgorithm {
		NESTED_LOOP, SORT_MERGE
	};

	private List<ConceptAssertion> getNormalJoinResults(
			List<ConceptAssertion> left, List<ConceptAssertion> right,
			JoinAlgorithm type) {
		List<ConceptAssertion> result = new LinkedList<ConceptAssertion>();
		if (type.equals(JoinAlgorithm.NESTED_LOOP)) {
			for (ConceptAssertion ca : left) {
				for (ConceptAssertion da : right) {
					if (da.getIndividual().equals(ca.getIndividual())) {
						result.add(new ConceptAssertion(
								ca.getIndividual(),
								new AndConcept(ca.getConcept(), da.getConcept())));
						// an individual only appears once in the ABox
						break;
					}
				}
			}
		} else if (type.equals(JoinAlgorithm.SORT_MERGE)) {

		}
		return result;
	}

	private List<ConceptAssertion> EvaluateProjectionQuery(ProjectionOP pq) {
		ProjectionOPEvaluator eval = new ProjectionOPEvaluator(
				pq.getPlanEvalKB());
		return pq.acceptVisitor(eval);
	}

	public ConceptAssertion EvaluateSelectionQueryPipelined(SelectionOP q,
			ConceptAssertion pos) throws CoreReasoningException {
		DescriptionLogicsKB kb = q.getPlanEvalKB();
		// if(kb == null){
		// System.out.println("No KB found for this query to evaluate.");
		// }
		if (isSpecialAssertion(pos)) {
			StarConceptAssertion sca = (StarConceptAssertion) pos;
			KBReason rs = new KBReason();
			if (rs.subsumes(sca.getConcept(), q.getSelectionCondition(), kb)) {
				// test if K |= D under C. if yes, populate the results.
				return sca;
			} else {
				return null;
			}
		}
		// K U {a:D} |= a:C?
		ConceptAssertion rhs = new ConceptAssertion(pos.getIndividual(),
				q.getSelectionCondition());
		KBReason rs = new KBReason();
		boolean check = rs.logicConsequence(pos, rhs, kb);
		if (check) {
			return pos;
		}
		return null;
	}

	private List<ConceptAssertion> EvaluateSelectionQuery(SelectionOP q)
			throws CoreReasoningException {
		List<ConceptAssertion> res = new LinkedList<ConceptAssertion>();
		List<ConceptAssertion> pos = visit(q.getSubquery());
		DescriptionLogicsKB kb = q.getPlanEvalKB();
		if (kb == null) {
			System.out.println("No KB found for this query to evaluate.");
		}
		// if(isSpecialAssertion(pos)){
		// //star semantics
		// StarConceptAssertion sca = (StarConceptAssertion) pos.get(0);
		// KBReason rs = new KBReason();
		// if(rs.subsumes(sca.getConcept(), q.getSelectionCondition(), kb)){
		// // test if K |= D under C. if yes, populate the results.
		// if(kb.getAbox() != null){
		// return kb.getAbox().populateStarConceptAssertion(sca);
		// }else{
		// return new LinkedList<ConceptAssertion> ();
		// }
		// }else{
		// //populate assertions for testing
		// if(kb.getAbox() != null){
		// pos = kb.getAbox().populateStarConceptAssertion(sca);
		// }
		// }
		// }
		if (isSpecialAssertion(pos)) {
			StarConceptAssertion sca = (StarConceptAssertion) pos.get(0);
			KBReason rs = new KBReason();
			if (rs.subsumes(sca.getConcept(), q.getSelectionCondition(), kb)) {
				// test if K |= D under C. if yes, populate the results.
				res.add(sca);
			}
			// else return empty result set
			return res;
		}
		// build an index tree for subquery C results, and search among it the
		// nodes a:D s.t.
		// K U {a:D} |= a:C?
		for (ConceptAssertion ca : pos) {
			ConceptAssertion rhs = new ConceptAssertion(ca.getIndividual(),
					q.getSelectionCondition());
			KBReason rs = new KBReason();
			boolean check = rs.logicConsequence(ca, rhs, kb);
			if (check) {
				res.add(ca);
			}
		}
		return res;
	}

	/**
	 * Indexing the query results wrt the given ordering description, a tbox and
	 * an abox.
	 * 
	 * @param res
	 * @param od
	 * @return
	 * @throws CoreReasoningException
	 */
	public static DescriptionIndex indexingQueryResults(
			List<ConceptAssertion> res, OrderDep od)
			throws CoreReasoningException {
		DescriptionIndex idx = new DescriptionIndex(null, od);
		try {
			idx.load(res);
		} catch (OrderingDescriptionException e) {
			e.printStackTrace();
		}
		return idx;
	}

}
